package libra.gatewayListener;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.cloud.endpoint.event.RefreshEvent;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.InMemoryRouteDefinitionRepository;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.nacos.api.exception.NacosException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;

import libra.MainEntry;
import reactor.core.publisher.Mono;

/**
 * Gateway中处理SpringEvent事件监听
 * 与Spring中一样，有：实现ApplicationListener接口；使用@EventListener注解；两种实现方式
 * 本组件采用试用@EventListener注解
 * 实现ApplicationListener接口参见本工程libra.gatewayListener.implByApplicationListener包下实现
 */
@Component
public class GatewayEventListenerHandler {
	public static final ObjectMapper objectMapper = new ObjectMapper();
	public static final JavaType javaType = objectMapper.getTypeFactory().constructParametricType(ArrayList.class, RouteDefinition.class);
	
	@Value("#{ '${spring.application.name}' + '.' + '${spring.cloud.nacos.config.file-extension}' }")
	public String dataId;
	
	/**
	 * 获取com.alibaba.cloud.nacos.NacosConfigAutoConfiguration(spring-cloud-starter-alibaba-nacos-config.jar)配置类注册的NacosConfigManager
	 * 通过NacosConfigManager我们可以获取到ConfigService对象，通过ConfigService进行API操作
	 */
	@Autowired
	NacosConfigManager nacosConfigManager;
	
	/**
	 * 获取EventPublisher
	 * 发布org.springframework.cloud.gateway.event.RefreshRoutesEvent.RefreshRoutesEvent事件 
	 */
	@Autowired
	ApplicationEventPublisher publisher;
	
	/**
	 * 获取容器中存在的RouteDefinitionRepository实例(InMemoryRouteDefinitionRepository)
	 * inMemoryRouteDefinitionRepository实现自RouteDefinitionRepository
	 * RouteDefinitionRepository继承自RouteDefinitionLocator、RouteDefinitionWriter
	 */
	@Autowired
    InMemoryRouteDefinitionRepository inMemoryRouteDefinitionRepository;
	
	
	/*************************************
	 ********** Gateway事件监听 **********
	 *************************************/
	/**
	 * !!!：需要注意，我们需要确保在容器初始化完成后，才能通过ConfigService进行API操作，否则可能会导致应用的执行被阻塞
	 * 应用启动完成后触发
	 * 	在应用启动就绪阶段会触发多个事件，顺序依次为：
	 * 		ReactiveWebServerInitializedEvent...
	 * 		ApplicationStartedEvent...
	 * 		ApplicationReadyEvent...
	 */
	@EventListener
	public void appAlready(ApplicationReadyEvent event) throws NacosException {
		String loadConfig = nacosConfigManager.getConfigService().getConfig(dataId, "DEFAULT_GROUP", 500);
		System.out.println( "NacosConfig加载..."+ dataId + "最新内容：\r\n" + loadConfig );
		loadNacosConfig(loadConfig);
	}
	/**
	 * Config被编辑更改时触发
	 */
	@EventListener
	public void configRefresh(RefreshEvent event) throws NacosException {
		String loadConfig = nacosConfigManager.getConfigService().getConfig(dataId, "DEFAULT_GROUP", 500);
		System.out.println( "NacosConfig更新..."+ dataId + "最新内容：\r\n" + loadConfig );
		loadNacosConfig(loadConfig);
	}
	
	
	
	
	
	/*************************************
	 *********** Config处理函数 
	 *************************************/
	public void publishConfig( Object data ) throws Exception {
		String json = "[]";
		if( data != null ) {
			json = objectMapper.writeValueAsString(data);
		}
		nacosConfigManager.getConfigService().publishConfig(dataId, "DEFAULT_GROUP", json, "json");
	}
	
	/*
	 * 内部调用方法，用于处理NacosConfig的结果
	 * 通过当前应用的inMemoryRouteDefinitionRepository进行Route更新
	 * 完成更新后，发送RefreshRoutesEvent刷新事件
	 */
	public void loadNacosConfig( String configInfo ) {
		if( configInfo==null || configInfo.isBlank()  ) {
			return;
		}
		try {
			// 将NacosConfig固化的Route列表反序列化为List
			List<RouteDefinition> routeConfig = objectMapper.readValue(configInfo, javaType);
			
			// 从本地inMemoryRouteDefinitionRepository中获取缓存的RouteDefinition列表
			Map<String,RouteDefinition> localAll = getLocalAll(inMemoryRouteDefinitionRepository);
			
			// 清空本地缓存
			localAll.keySet().forEach( 
				routeId -> {
					inMemoryRouteDefinitionRepository
						// 调用InMemoryRouteDefinitionRepository的delete实现，对RouteDefinition进行删除
						.delete(Mono.just(routeId))
						.subscribe();// 必须调用subscribe(),否则流式编程不会执行
				} 
			);
			// 添加远端存储
			routeConfig.forEach( 
				routeDefinition -> {
					inMemoryRouteDefinitionRepository
						// 调用InMemoryRouteDefinitionRepository的save实现，对RouteDefinition进行保存
						.save(Mono.just(routeDefinition))
						.subscribe();// 必须调用subscribe(),否则流式编程不会执行
				} 
			);
			/* 发布RefreshRoutesEvent时间，通知CachingRouteLocator更新缓存Route列表 */
			publisher.publishEvent(new RefreshRoutesEvent(this));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	// 内部调用方法，用于将从inMemoryRouteDefinitionRepository中获取的RouteDefinition列表转化为Map
	public Map<String, RouteDefinition> getLocalAll( InMemoryRouteDefinitionRepository inMemoryRouteDefinitionRepository ) {
		HashMap<String, RouteDefinition> map = new HashMap<String, RouteDefinition>();
		inMemoryRouteDefinitionRepository.getRouteDefinitions()
			.collectMap( obj -> { return obj.getId(); })
	    	.doOnNext( obj -> { map.putAll(obj);	})
	    	.subscribe( );// 必须调用subscribe(),否则流式编程不会执行
		return map;
	}
}
